"""
比赛管理模块
"""

import contextlib
import json
import time
from collections import OrderedDict, defaultdict
from datetime import datetime
from enum import IntEnum
from typing import List

from flask import current_app, g, request
from flask_restx import abort, reqparse
from sqlalchemy import and_


import traceback
from app.models import ctfModel, rangeModel, classesModel
from app.models.ctfModel import (
    CtfRedBlue,
    CtfRedBlueFlag,
    CtfRedBlueGroup,
    CtfRedBlueManualAudit,
    CtfRedBlueScore,
    GroupType,
    LinkCtfRedBlueScoreInfo,
    RangeLinkModule,
    User, CtfNotice,
)
from tools.basic_tools import Tools
from app.models.roleModel import PresetRoleChoices
from tools import db
from tools import redis_conn as redis_store
from utils.utils import base_detail, get_link_range_model
# 触发关闭比赛任务
from tasks.tasks import close_ctf_red_blue, revoke_task

from . import member
from .base_view import OrganizerBase


class MathManage(OrganizerBase):

    # 获取用户信息
    def getCtfInfo(self, id=None, name=None):
        if name:
            ctfInfo = ctfModel.Ctf.query.filter_by(name=name).first()
        if id:
            ctfInfo = ctfModel.Ctf.query.filter_by(id=id, user_id=self.user.id).first()
        return ctfInfo

    def checkCTF(self, ctf_id):
        if not ctf_id:
            abort(400, message="参数错误22")

        ctfInfo = self.getCtfInfo(id=ctf_id)
        if not ctfInfo:
            abort(400, message="ctf不存在")

        return ctfInfo

    def getQuestionScore(self, question_ids):
        ctfInfo = ctfModel.CtfQuestion.query.filter(
            ctfModel.CtfQuestion.id.in_(question_ids)
        ).all()
        return sum(int(s.score) for s in ctfInfo)

    def celery_task_ctf(self, ctf_id, start_time, end_time):
        from tasks.tasks import close_ctf, start_ctf, add_job_to_scheduler, revoke_task

        start_task_key = f"CtfTaskStart::{ctf_id}"
        close_task_key = f"CtfTaskCLose::{ctf_id}"

        # 检查是否有已注册任务, 存在则取消
        revoke_task([start_task_key, close_task_key])
        # 开启比赛
        add_job_to_scheduler(
            task_id=start_task_key, 
            func=start_ctf,
            trigger="date",
            args=[ctf_id],
            **{"run_date": Tools.timeStamp_to_strftime(start_time), 
            "func_name": "start_ctf"}
            )
        # 关闭比赛
        
        # # 保存任务ID
        redis_store.set(start_task_key, ctf_id, ex=int(end_time + 5))

    def check_ctf_red_blue_exists(self, ctf_red_blue_id) -> CtfRedBlue:
        """检查红蓝比赛是否存在"""
        if not ctf_red_blue_id:
            abort(400, message="缺少 红蓝比赛ID")

        ctf_red_blue = CtfRedBlue.query.filter(CtfRedBlue.id == ctf_red_blue_id)
        if ctf_red_blue.count() != 1:
            abort(400, message="当前比赛不存在！")

        ctf = ctf_red_blue.first()

        current_role = request.cookies.get("role", None)
        if not current_role:
            abort(400, message="cookies错误")
        if any(
                [
                    current_role == PresetRoleChoices.rb_pilot
                    and ctf.rb_pilot_id != self.user.id,
                    current_role == PresetRoleChoices.rb_referee
                    and ctf.rb_referee_id != self.user.id,
                    current_role == PresetRoleChoices.teacher
                    and ctf.create_user_id != self.user.id,
                ]
        ):
            abort(403, message="当前用户无权操作该比赛")
        return ctf

    def celery_task_red_blue(self, ctf_id, params, start_time, end_time):
        """注册红蓝攻防比赛开启、关闭任务"""
        from tasks.tasks import close_ctf_red_blue, start_ctf_red_blue, add_job_to_scheduler, revoke_task

        start_task_key = f"CtfRedBlueStart__{ctf_id}"
        close_task_key = f"CtfRedBlueCLose__{ctf_id}"

        # 检查是否有已注册任务, 存在则取消
        revoke_task([start_task_key, close_task_key])
        # 开启比赛
        add_job_to_scheduler(
            task_id=start_task_key, 
            func=start_ctf_red_blue,
            trigger="date",
            args=ctf_id,
            args_dict = params,
            **{"run_date": Tools.timeStamp_to_strftime(start_time), 
            "func_name": "start_ctf_red_blue"}
            )
        # start_task = start_ctf_red_blue.apply_async([ctf_id, params], countdown=start_time)
        # 关闭比赛
        # close_task = close_ctf_red_blue.apply_async([ctf_id], countdown=end_time)

        # 保存任务ID
        redis_store.set(start_task_key, ctf_id, ex=int(end_time + 5))


class CTFmathList(MathManage):
    def get_student_commit_question_total(self, ctf_id):
        """
        获取所有学生提交赛题数量
        """
        result_data = []
        rows = ctfModel.CtfLinkUser.query.filter_by(ctf_id=ctf_id).all()
        if rows:
            for row in rows:
                user_obj = db.session.query(User).filter_by(id=row.user_id).first()
                result_data.append({"name": user_obj.username, "num": row.questions})
        return result_data

    def get_student_replied_question_total(self, ctf_id):
        # sourcery skip: avoid-builtin-shadow
        """
        获取学生已答复赛题数量
        misc = 1, "MISC(杂项)"
        crypto = 2, "CRYPTO(加密)"
        pwn = 3, "PWN(二进制漏洞利用)"
        reverse = 4, "Reverse(逆向工程)"
        industrial_control = 5, "工控"
        """
        misc = 0
        crypto = 0
        pwn = 0
        reverse = 0
        industrial_control = 0
        flag_rows = ctfModel.CtfUserFlag.query.filter_by(ctf_id=ctf_id).all()
        for flag in flag_rows:
            ctf_link_question_id = flag.ctf_link_question_id
            linkQuestion = ctfModel.CtfLinkQuestion.query.filter_by(
                ctf_id=ctf_id, id=ctf_link_question_id
            ).first()
            type = linkQuestion.question[0].type
            if type == 1:
                misc += 1
            elif type == 2:
                crypto += 1
            elif type == 3:
                pwn += 1
            elif type == 4:
                reverse += 1
            elif type == 5:
                industrial_control += 1
        return {"misc_count": misc, "crypto_count": crypto, "pwn_count": pwn, "reverse_count": reverse,
                "industrial_control_count": industrial_control}

    def get_student_commit_question_info(self, ctf_id):
        """
        展示每个学生提交赛题的实时情况
        """
        audit_list = []
        rows = ctfModel.CtfUserFlagAuditLog.query.filter_by(ctf_id=ctf_id).all()
        if rows:
            audit_list.extend(
                {
                    "checkpoint": row.checkpoint,
                    "stu_name": row.stu_name,
                    "log_str": row.log_str,
                    "question_name": row.question_name,
                    "question_flag": row.question_flag,
                    "audit_score": row.audit_score,
                }
                for row in rows
            )
        return audit_list

    def get_countdown(self, end_date):
        """
        比赛倒计时
        """
        ctime = int(time.time())
        return max(end_date - ctime, 0)

    def get(self):
        params = request.args
        page = params.get("page", 1)
        status = 0
        status = params.get("status")
        keyword = params.get("keyword")
        page_size = params.get("pageSize", 10)
        try:
            page = int(page)
            page_size = int(page_size)
        except Exception:
            page = 1
            page_size = 10

        if page_size >= 50:
            abort(400, message="每页数量不能超过50")

        try:
            filters = []
            if keyword:
                # 添加关键词的检索选项
                filters.append(ctfModel.Ctf.name.contains(keyword))

            if status:
                if int(status) not in [0, 1, 2]:
                    abort(400, message="参数错误")
                filters.append(ctfModel.Ctf.status == status)

            paginate = (
                ctfModel.Ctf.query.filter(*filters)
                .filter_by(user_id=self.user.id)
                .order_by(ctfModel.Ctf.id.desc())
                .paginate(page, page_size, error_out=False)
            )
            info = {}
            info["current_page"] = page
            info["data"] = []
            if len(paginate.items) > 0:
                for content in paginate.items:
                    # 比赛列表返回关联靶场的模板类型，便于前端3D模型渲染
                    if content.range_uuid:
                        _ranges = (
                            rangeModel.Range.query.join(
                                rangeModel.RangeLinkModule,
                                rangeModel.RangeLinkModule.range_id == rangeModel.Range.id,
                            )
                            .join(
                                ctfModel.Ctf,
                                and_(
                                    ctfModel.Ctf.range_uuid
                                    == rangeModel.RangeLinkModule.range_uuid,
                                    ctfModel.Ctf.id == content.id,
                                ),
                            )
                            .filter(rangeModel.Range.is_system == 0)
                        )
                        range_template_type = _ranges.first().range_template_type
                    else:
                        range_template_type = ""
                    info["data"].append(
                        {
                            "name": content.name,
                            "range_uuid": content.range_uuid,
                            "describe": content.describe,
                            "start_date": content.start_date,
                            "end_date": content.end_date,
                            "id": content.id,
                            "type": content.type,
                            "status": content.status,
                            "range_template_type": range_template_type,
                            "countdown": self.get_countdown(content.end_date),
                            "all_student_commit_question_total": self.get_student_commit_question_total(content.id),
                            "student_replied_question_total": self.get_student_replied_question_total(content.id),
                            "get_student_commit_question_info": self.get_student_commit_question_info(content.id)
                        }
                    )
            info = base_detail(info, paginate)
            return {"code": 200, "data": info}

        except Exception as e:
            current_app.logger.exception(e)
            print(traceback.print_exc())
            abort(500, message="参数错误")


class CTFmathStatus(MathManage):
    """该接口当前仅有关闭功能"""

    # 开启关闭ctf比赛
    def post(self, ctf_id):
        # 校验ctf_id是否存在
        ctfInfo = self.checkCTF(ctf_id)
        parser = reqparse.RequestParser()
        parser.add_argument("status", type=int, help="填写状态", required=True)
        args = parser.parse_args()
        if args["status"] == 0:
            timeStamp = time.time()
            args["end_date"] = timeStamp

        if not all([ctfInfo]):
            abort(400, message="参数错误")

        status = [0, 1]  # 0：关闭，1开启
        if args["status"] not in status:
            abort(400, message="参数错误")

        with db.auto_commit():
            args["status"] = 2
            ctfModel.Ctf.query.filter_by(id=ctf_id).update(args)
            current_app.logger.info(
                "CTFmathStatus---action--put--ctf-status:"
                + str(args["status"])
                + "--ctf_id:"
                + str(ctf_id)
            )
            # set_redis_value(args['range_uuid'] + "_" + args['courseware_uuid'] + "_" + args['chapter_uuid'],
            #                 env_end_time - int(time.time()), 1)
            return {"code": 200, "data": "操作成功"}


class CTFmath(MathManage):
    @staticmethod
    def get_range_info(range_uuid):
        if not range_uuid:
            return None
        range_model: RangeLinkModule = RangeLinkModule.query.filter(
            RangeLinkModule.range_uuid == range_uuid
        ).first()
        return {
            "id": range_model.id,
            "name": range_model.Range.name,
            "range_uuid": range_model.range_uuid,
        }

    def get(self, ctf_id):
        # 校验ctf_id是否存在
        ctfinfo: ctfModel.Ctf = self.checkCTF(ctf_id)
        info = []
        info.append(
            {
                "id": ctfinfo.id,
                "name": ctfinfo.name,
                "type": ctfinfo.type,
                "start_date": ctfinfo.start_date,
                "end_date": ctfinfo.end_date,
                "describe": ctfinfo.describe,
                "range_info": self.get_range_info(ctfinfo.range_uuid),
            }
        )
        return {"code": 200, "data": info}

    @staticmethod
    def link_range_for_this(range_uuid, ctf_id):
        """为当前比赛自动关联场景"""
        from app.api.v1.organizer.rangeEnv import RangeCollect

        range_model = RangeLinkModule.query.filter(
            RangeLinkModule.range_uuid == range_uuid
        ).first()
        data = RangeCollect.ctf_range_link(range_model, ctf_id=ctf_id)
        if data["code"] == 200:
            return
        else:
            abort(400, message="关联场景失败")

    @staticmethod
    def clean_temp_lock(link_range_uuid, ctf_name):
        try:
            pipe = redis_store.pipeline()
            pipe.delete(f"TEMP_CTF_RANGE_LOCK::{link_range_uuid}")
            pipe.delete(ctf_name)
            pipe.execute()
        except Exception as e:
            current_app.logger.error(f"ctf比赛: {ctf_name}已创建，清除临时靶场锁失败", exc_info=e)
            return

    @staticmethod
    def check_issue_from(range_uuid: str, question_list: List[List]):
        """检查赛题来源"""
        range_info: RangeLinkModule = RangeLinkModule.query.filter(
            RangeLinkModule.range_uuid == range_uuid
        ).first()
        if not {
            val.id for item in range_info.vmList for val in item.bind_question
        }.issuperset({val for item in question_list for val in item}):
            abort(400, message="选择的赛题并非全部来自关联场景中绑定的赛题", clean_issue=True)

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("name", help="比赛名称不能为空", required=True)
        parser.add_argument("type", help="比赛类型", type=int, required=True)
        parser.add_argument("describe", help="比赛描述", required=True)
        parser.add_argument("question", help="课题信息不能为空", required=True)
        parser.add_argument("userlist", help="用户信息不能为空", required=True)
        parser.add_argument("start_date", type=int, help="开始时间不能为空", required=True)
        parser.add_argument("end_date", type=int, help="结束时间不能为空", required=True)
        args = parser.parse_args()

        if not all(
                [args["name"], args["describe"], args["start_date"], args["end_date"]]
        ):
            abort(400, message="参数错误")

        if args["type"] > 1:
            abort(400, message="参数错误")

        # 验证开始时间结束时间,开始时间不能小于当前时间，结束时间不能大于开始时间
        curr_time = int(time.time())
        if args["start_date"] < curr_time or args["start_date"] > args["end_date"]:
            abort(400, message="开始时间不能小于当前时间，结束时间不能大于开始时间")

        # 比赛时长最长1周限制
        limiter = 86400 * 7
        if args["end_date"] - args["start_date"] > limiter:
            abort(400, message="请控制比赛时长在一周以内！")

        questionList = request.get_json()["question"]
        userlist = request.get_json()["userlist"]
        if not isinstance(questionList, list):
            abort(400, message="question格式错误")
        if not isinstance(userlist, list):
            abort(400, message="userlist格式错误")

        # 校验ctf名称是否重复
        ctfInfo = self.getCtfInfo(name=args["name"])
        if ctfInfo:
            abort(400, message="名称重复")

        # 判断是否需要自动关联场景
        link_range_uuid = None
        if args["type"] == 1:
            link_range_uuid = redis_store.get(args["name"])
            if not link_range_uuid:
                abort(400, message="操作超时，请返回上一步重新选择场景并选择赛题", clean_issue=True)
            else:
                link_range_uuid = link_range_uuid.decode()
                if link_range_uuid in get_link_range_model():
                    abort(400, message="该虚拟机已关联比赛或课程，不允许关联到比赛或课程", clean_issue=True)
                self.check_issue_from(link_range_uuid, questionList)

        try:
            # 写入基本信息
            for ques in questionList[:]:
                if not ques:
                    questionList.remove(ques)
            obj = ctfModel.Ctf(
                name=args["name"],
                describe=args["describe"],
                start_date=args["start_date"],
                end_date=args["end_date"],
                type=args["type"],
                level=len(questionList),
                user_id=self.user.id,
            )
            ctfModel.db.session.add(obj)
            ctfModel.db.session.flush()
            # 写入题目list
            ctfQ = []
            level_score = {}
            i = 1
            for ques in questionList:
                if not ques:
                    continue
                # 查询是否存在课题
                if not isinstance(ques, list):
                    abort(400, message="参数错误")
                for q in ques:
                    ctfQ.append(
                        ctfModel.CtfLinkQuestion(
                            ctf_id=obj.id, ctf_question_id=q, level_number=i
                        )
                    )
                level_score[i] = self.getQuestionScore(ques)
                i = i + 1
            # 写入用户列表
            userL = []
            for user in userlist:
                # 校验用户是否存在
                if not member.Member.getUserInfo(self, id=user):
                    abort(400, message="参数错误")
                userL.append(ctfModel.CtfLinkUser(ctf_id=obj.id, user_id=user))

            # 写入关卡信息
            ctfLevel = []
            ctfLevel.append(
                ctfModel.CtfLevel(
                    ctf_id=obj.id, level_number_score=json.dumps(level_score)
                )
            )

            db.session.add_all(ctfQ)
            db.session.add_all(userL)
            db.session.add_all(ctfLevel)

            # 自动关联场景
            if args["type"] == 1 and link_range_uuid:
                self.link_range_for_this(link_range_uuid, ctf_id=obj.id)
                self.clean_temp_lock(link_range_uuid, args["name"])

            db.session.commit()

            self.celery_task_ctf(
                obj.id, start_time=args["start_date"], end_time=args["end_date"]
            )
        except Exception as e:
            current_app.logger.error(f"创建比赛{args['name']}失败", exc_info=e)
            db.session().rollback()
            abort(400, message="比赛创建失败")
        else:
            return {"code": 200, "data": "创建成功"}

    def put(self, ctf_id):
        # 校验ctf_id是否存在
        ctfInfo = self.checkCTF(ctf_id)
        if ctfInfo.status == 1:
            abort(400, message="不能修改已开启的比赛")

        parser = reqparse.RequestParser()
        parser.add_argument("name", help="比赛名称不能为空", required=True)
        # parser.add_argument('type', help='比赛类型', type=int, required=True)
        parser.add_argument("describe", help="比赛描述", required=True)
        parser.add_argument("question", help="课题信息不能为空", required=True)
        parser.add_argument("userlist", help="用户信息不能为空", required=True)
        parser.add_argument("start_date", type=int, help="开始时间不能为空", required=True)
        parser.add_argument("end_date", type=int, help="结束时间不能为空", required=True)
        args = parser.parse_args()

        # if args['type'] > 1:
        #     abort(400, message="参数错误")

        # 验证开始时间结束时间,开始时间不能小于当前时间，结束时间不能大于开始时间
        curr_time = int(time.time())
        if args["start_date"] < curr_time or args["start_date"] > args["end_date"]:
            abort(400, message="开始时间不能小于当前时间，结束时间不能小于开始时间")

        # 比赛时长最长1周限制
        limiter = 86400 * 7
        if args["end_date"] - args["start_date"] > limiter:
            abort(400, message="请控制比赛时长在一周以内！")

        questionList = request.get_json()["question"]
        userlist = request.get_json()["userlist"]
        if not isinstance(questionList, list):
            abort(400, message="question格式错误")
        if not isinstance(userlist, list):
            abort(400, message="userlist格式错误")

        # 校验ctf名称是否重复
        ctfNameInfo = self.getCtfInfo(name=args["name"])
        if ctfNameInfo and ctfNameInfo.id != ctf_id:
            return {"code": 400, "data": "名称重复"}

        # 判断是否需要自动关联场景
        link_range_uuid = None
        if ctfInfo.type == 1:
            link_range_uuid = redis_store.get(args["name"])
            if not link_range_uuid:
                abort(400, message="操作超时，请返回上一步重新选择场景并选择赛题", clean_issue=True)
            else:
                link_range_uuid = link_range_uuid.decode()
                self.check_issue_from(link_range_uuid, questionList)
        for ques in questionList[:]:
            if not ques:
                questionList.remove(ques)
        with db.auto_commit():
            # 修改基本信息
            ctfInfo.name = args["name"]
            ctfInfo.status = 0  # 修改成待开始比赛
            ctfInfo.level = len(questionList)
            ctfInfo.describe = args["describe"]
            ctfInfo.start_date = args["start_date"]
            ctfInfo.end_date = args["end_date"]
            # 更新题目list
            ctfQ = []
            i = 1
            # 删除比赛关联内容
            ctfModel.CtfLinkQuestion.query.filter_by(ctf_id=ctf_id).delete()
            ctfModel.CtfLinkUser.query.filter_by(ctf_id=ctf_id).delete()
            ctfModel.CtfLevel.query.filter_by(ctf_id=ctf_id).delete()
            ctfModel.CtfUserFlag.query.filter_by(ctf_id=ctf_id).delete()
            level_score = {}
            for ques in questionList:
                if not ques:
                    continue
                # 查询是否存在课题
                if not isinstance(ques, list):
                    abort(400, message="参数错误")

                ctfQ.extend(
                    ctfModel.CtfLinkQuestion(
                        ctf_id=ctf_id, ctf_question_id=q, level_number=i
                    )
                    for q in ques
                )
                score_sum = self.getQuestionScore(ques)
                level_score[i] = score_sum
                i += 1

            # 写入用户列表
            userL = []
            for user in userlist:
                # 校验用户是否存在
                if not member.Member.getUserInfo(self, id=user):
                    abort(400, message="参数错误")
                userL.append(ctfModel.CtfLinkUser(ctf_id=ctf_id, user_id=user))

            # 写入关卡信息
            ctfLevel = [
                ctfModel.CtfLevel(
                    ctf_id=ctf_id, level_number_score=json.dumps(level_score)
                )
            ]
            db.session.add_all(ctfQ)
            db.session.add_all(userL)
            db.session.add_all(ctfLevel)

            # 重新绑定场景
            if ctfInfo.type == 1 and link_range_uuid:
                ctfInfo.range_uuid = link_range_uuid
                self.clean_temp_lock(link_range_uuid, args["name"])

            db.session.add(ctfInfo)
            db.session.commit()
            self.celery_task_ctf(
                ctf_id, start_time=args["start_date"], end_time=args["end_date"]
            )
        return {"code": 200, "data": "修改成功"}

    def delete(self, ctf_id=None):
        """
        删除比赛
        1、判断比赛是否开启，开启不能操作删除
        2、判断是否有拓扑生成，存在-》删除拓扑
        """
        # 校验ctf_id是否存在
        ctfInfo = self.checkCTF(ctf_id)
        if ctfInfo.status == 1:
            return {"code": 400, "data": "不能修改已开启的比赛！"}

        with db.auto_commit():
            ctfModel.CtfLinkQuestion.query.filter_by(ctf_id=ctf_id).delete()
            ctfModel.CtfLinkUser.query.filter_by(ctf_id=ctf_id).delete()
            ctfModel.CtfLevel.query.filter_by(ctf_id=ctf_id).delete()
            ctfModel.CtfUserFlag.query.filter_by(ctf_id=ctf_id).delete()
            # 删除竞技比flag提交记录
            ctfModel.CtfUserFlagAuditLog.query.filter_by(ctf_id=ctf_id).delete()
            db.session.delete(ctfInfo)
            current_app.logger.info(
                self.user.username + "---action--delete--ctf:" + str(ctf_id)
            )
        return {"code": 200, "data": "删除成功！"}


# 临时对ctf有场景类型和选择的场景进行锁定
class CTFTempRangeLock(MathManage):
    def post(self):
        parser = reqparse.RequestParser(bundle_errors=True)
        parser.add_argument("ctf_name", help="靶场ID不能为空", required=True)
        parser.add_argument("range_uuid", help="靶场ID不能为空", required=True)
        parser.add_argument("ctf_id", default=None, type=int)
        kwargs = parser.parse_args()

        _filter = [ctfModel.Ctf.name == kwargs["ctf_name"]]
        if kwargs["ctf_id"]:
            _filter.append(ctfModel.Ctf.id != kwargs["ctf_id"])

        if ctfModel.Ctf.query.filter(*_filter).all():
            abort(400, message="比赛名称重复")

        current_ctf_range_uuid = current_ctf_name = -1
        current_ctf = (
            ctfModel.Ctf.query.get(kwargs["ctf_id"]) if kwargs["ctf_id"] else None
        )
        if current_ctf:
            current_ctf_range_uuid = current_ctf.range_uuid
            current_ctf_name = current_ctf.name

        if (
                kwargs["range_uuid"] != current_ctf_range_uuid
                and kwargs["range_uuid"] in get_link_range_model()
        ):
            abort(400, message="该虚拟机已关联比赛或课程，不允许关联到比赛或课程")

        # 其他比赛锁定/自身锁定
        ctf_name = redis_store.get(f"TEMP_CTF_RANGE_LOCK::{kwargs['range_uuid']}")
        if ctf_name and ctf_name.decode() not in (
                kwargs["ctf_name"],
                current_ctf_name,
        ):
            abort(400, message=f"当前靶场已被ctf比赛{ctf_name.decode()}临时锁定，请选择其他靶场")

        # 清除可能存在的旧场景锁定，并锁定新预关联场景
        temp_lock_ctf_range_lua = """
            local per = 'TEMP_CTF_RANGE_LOCK::'

            local range_uuid = KEYS[1]
            local ctf_key = KEYS[2]
            local expire_time = ARGV[1]

            local range_lock_key = per .. range_uuid
            local old_range_lock = redis.call("GET", ctf_key)
            if old_range_lock then
                redis.call("DEL", per .. old_range_lock)
                return 'TEMP_CTF_RANGE_LOCK::' .. old_range_lock
            end

            redis.call("SETEX", ctf_key, expire_time, range_uuid)
            redis.call("SETEX", range_lock_key, expire_time, ctf_key)
        """

        try:
            cmd = redis_store.register_script(temp_lock_ctf_range_lua)
            cmd(keys=[kwargs["range_uuid"], kwargs["ctf_name"]], args=[120])
        except Exception as e:
            current_app.logger.error("临时锁定ctf比赛与靶场关系失败", exc_info=e)
            abort(400, "锁定选择场景失败，请返回上一步重新操作")
        else:
            return {"code": "200", "data": ""}


class CTFmathQuestion(MathManage):
    # 获取问题列表
    def get(self, ctf_id):
        self.checkCTF(ctf_id)
        ctfQuestionList = ctfModel.CtfLinkQuestion.query.filter_by(ctf_id=ctf_id).all()
        info = {}

        for content in ctfQuestionList:
            if content.level_number not in info:
                info[content.level_number] = []
            if content.to_dict():
                info[content.level_number].append(content.to_dict())

        return {"code": 200, "data": info}


class CTFmathUser(MathManage):
    # 获取问题列表
    def get(self, ctf_id):
        self.checkCTF(ctf_id)
        ctfUserList = ctfModel.CtfLinkUser.query.filter_by(ctf_id=ctf_id).all()
        info = [
            {
                "username": content.userinfo.username,
                "email": content.userinfo.email,
                "realname": content.userinfo.account_name,
                "id": content.userinfo.id,
            }
            for content in ctfUserList
            if content.userinfo
        ]
        return {"code": 200, "data": info}


class CTFmathRange(MathManage):
    def put(self, ctf_id):
        ctfInfo = self.checkCTF(ctf_id)

        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场不能为空", required=True)
        args = parser.parse_args()

        if not all([args["range_uuid"]]):
            abort(400, message="参数错误")

        rangeInfo = rangeModel.RangeLinkModule.query.filter_by(
            range_uuid=args["range_uuid"], ctf_id=ctf_id
        ).first()
        if not rangeInfo:
            abort(400, message="靶场环境不存在")

        with db.auto_commit():
            ctfModel.Ctf.query.filter_by(id=ctf_id).update(args)
            current_app.logger.info(
                "---action--put--ctf-range_uuid range_uuid:"
                + args["range_uuid"]
                + "--ctf_id:"
                + str(ctf_id)
            )
            return {"code": 200, "data": ctfInfo.to_dict()}


class CTFmathScore(MathManage):
    def get(self, ctf_id, user_id):
        ctfInfo = self.checkCTF(ctf_id)
        userInfo = {}
        for v in ctfInfo.userList:
            if v.user_id == user_id:
                userInfo[v.user_id] = v.userinfo.account_name
                break

        if not userInfo:
            abort(400, message="参数有误")

        ctf_question_score = ctfModel.CtfUserFlag.query.filter_by(
            ctf_id=ctf_id, user_id=user_id
        ).all()
        item = []
        for cqs in ctf_question_score:
            if not cqs.ctfLinkQuestion:
                abort(400, message="答题被删除")
            item.append(
                {
                    "name": cqs.ctfLinkQuestion.question[0].name,
                    "score": cqs.score,
                    "level_number": cqs.level_number,
                    "realname": userInfo[cqs.user_id],
                    "answer": cqs.ctfLinkQuestion.question[0].answer,
                }
            )
        return {"code": 200, "data": item}


class CTFmathListScore(MathManage):

    def get_countdown(self, end_date):
        """
        比赛倒计时
        """
        ctime = int(time.time())
        return max(end_date - ctime, 0)

    def get_student_commit_question_score_total(self, ctf_id):
        """
        获取所有学生提交赛题得分情况
        """
        result_data = []
        rows = ctfModel.CtfLinkUser.query.filter_by(ctf_id=ctf_id).all()
        if rows:
            for row in rows:
                user_obj = db.session.query(User).filter_by(id=row.user_id).first()
                result_data.append(
                    {"user_id": user_obj.id, "name": user_obj.username, "num": row.totalScore, "class_name":
                        classesModel.Classes.query.filter_by(id=user_obj.class_id).first().name
                        if classesModel.Classes.query.filter_by(id=user_obj.class_id).first() else ""})
        return result_data

    def get(self, ctf_id):
        ctfInfo = self.checkCTF(ctf_id)
        # 获取比赛倒计时
        countdown = self.get_countdown(ctfInfo.end_date)
        result_data = {"countdown": countdown, "type": "知识竞赛"}
        # 赛题数量
        questions_total = ctfModel.CtfLinkQuestion.query.filter_by(ctf_id=ctf_id).count()
        result_data["questions_total"] = questions_total
        questions_rows = ctfModel.CtfLinkQuestion.query.filter_by(ctf_id=ctf_id).all()
        score_total = sum(row.question[0].score for row in questions_rows)
        result_data["score_total"] = score_total
        # 所有学生信息
        all_stu_info = self.get_student_commit_question_score_total(ctf_id)
        result_data["all_stu_info"] = all_stu_info
        return {"code": 200, "data": result_data}


class OneStudentCTFmathListScore(MathManage):
    def get_question_type_count(self, ctf_id, user_id):
        """
        获取学生已答复赛题数量
        """
        misc = 0
        crypto = 0
        pwn = 0
        reverse = 0
        industrial_control = 0
        flag_data = ctfModel.CtfUserFlag.query.filter_by(ctf_id=ctf_id, user_id=user_id).all()
        for flag in flag_data:
            linkQuestion = ctfModel.CtfLinkQuestion.query.filter_by(ctf_id=ctf_id, id=flag.ctf_link_question_id).first()
            type = linkQuestion.question[0].type
            if type == 1:
                misc += 1
            elif type == 2:
                crypto += 1
            elif type == 3:
                pwn += 1
            elif type == 4:
                reverse += 1
            elif type == 5:
                industrial_control += 1
        return {"misc_count": misc, "crypto_count": crypto, "pwn_count": pwn, "reverse_count": reverse,
                "industrial_control_count": industrial_control}

    def get_commit_question_info(self, ctf_id, user_id):
        question_type = {
            1: "MISC(杂项)",
            2: "CRYPTO(加密)",
            3: "PWN(二进制漏洞利用)",
            4: "Reverse(逆向工程)",
            5: "工控"
        }
        flag_data = []
        flag_rows = ctfModel.CtfUserFlag.query.filter_by(ctf_id=ctf_id, user_id=user_id).all()
        for flag in flag_rows:
            ctf_link_question_id = flag.ctf_link_question_id
            linkQuestion = ctfModel.CtfLinkQuestion.query.filter_by(
                ctf_id=ctf_id, id=ctf_link_question_id
            ).first()
            name = linkQuestion.question[0].name
            answer = linkQuestion.question[0].answer
            type = linkQuestion.question[0].type
            score = linkQuestion.question[0].score
            flag_data.append(
                {"question_name": name, "question_answer": answer, "question_type": question_type[type], "question_score": score}
            )
        return flag_data

    def get(self, ctf_id, user_id):
        # 赛题类型占比
        question_ratio = self.get_question_type_count(ctf_id, user_id)
        result_data = {"question_ratio": question_ratio}
        flag_data = self.get_commit_question_info(ctf_id, user_id)
        result_data["flag_data"] = flag_data
        return {"code": 200, "data": result_data}


class CTFmonitor(MathManage):
    def get(self, ctf_id):
        ctfInfo = self.checkCTF(ctf_id)

        if ctfInfo.status != 1:
            abort(400, message="ctf比赛暂未开启，不能查看")
        ctf_user_total = (
            ctfModel.CtfLinkUser.query.filter_by(ctf_id=ctf_id)
            .order_by(ctfModel.CtfLinkUser.totalScore.desc())
            .all()
        )

        userList = [
            {
                "id": us.id,
                "name": us.userinfo.account_name,
                "score": us.totalScore,
                "questions": us.questions,
                "status": 1,
                "time": 1,
            }
            for us in ctf_user_total
        ]
        dynamic = (
            ctfModel.CtfUserFlag.query.filter_by(ctf_id=ctf_id)
            .order_by(ctfModel.CtfUserFlag.id.desc())
            .all()
        )

        dynamicList = [
            {
                "name": dy.userinfo.account_name,
                "level_number": dy.level_number,
                "create_time": dy.create_time.strftime("%Y-%m-%d %H:%M:%S"),
                "message": "提交答案",
            }
            for dy in dynamic
        ]
        return {
            "code": 200,
            "data": [
                {"userList": userList},
                {"dynamicList": dynamicList},
                {"level": ctfInfo.level},
            ],
        }


class CTFRedBlueQuestionCount(MathManage):
    """
    ctf红蓝对抗已答复赛题统计
    """

    def get(self, ctf_red_blue_id=None):
        misc = 0
        crypto = 0
        pwn = 0
        reverse = 0
        industrial_control = 0
        flag_rows = ctfModel.CtfRedBlueFlag.query.filter(CtfRedBlueFlag.ctf_red_blue_id == ctf_red_blue_id,
                                                         CtfRedBlueFlag.link_info_id != None).all()
        for flag in flag_rows:
            type = flag.ctf_question_type
            if type == 1:
                misc += 1
            elif type == 2:
                crypto += 1
            elif type == 3:
                pwn += 1
            elif type == 4:
                reverse += 1
            elif type == 5:
                industrial_control += 1
        return {
            "code": 200,
            "data": {"misc_count": misc, "crypto_count": crypto, "pwn_count": pwn, "reverse_count": reverse,
                     "industrial_control_count": industrial_control}
        }


class CTFRedBlueScoreCompare(MathManage):
    """
    每隔一小时红蓝分数对比
    """

    def get(self, ctf_red_blue_id=None):
        try:
            rows = ctfModel.CtfScore.query.filter_by(ctf_red_blue_id=ctf_red_blue_id). \
                    order_by(ctfModel.CtfScore.sort_id.asc()).all()
            if rows:
                res_list = [
                    {
                        "ctf_id": row.ctf_red_blue_id,
                        "sort_id": row.sort_id,
                        "score_info": json.loads(row.score_info),
                    }
                    for row in rows
                ]
                return {
                    "code": 200,
                    "data": res_list
                }
            else:
                return {
                    "code": 200,
                    "data": []
                }
        except Exception as e:
            print(e)
            abort(400, message="参数错误!")


class CTFRedBlueScreen(MathManage):
    """ctf红蓝大屏数据"""

    class AttackStatus(IntEnum):
        normal = 0
        attacked = 1
        breaking = 2

    def template_shunt(
            self, range_template_type: str, current_cache: dict, vm: rangeModel.RangeVm
    ):
        """
        按靶场模板处理plc状态
        :param string range_template_type: 靶场模板
        :param string current_cache: 当前靶场中PLC状态检查缓存
        :param string vm: 靶场中虚拟机
        """
        cache_key = (
            f"{current_app.config['CHECK_RANGE_PLC_STATUS_KEY']}::{vm.range_uuid}"
        )
        # 同一靶场range_uuid相同，无需反复获取缓存
        if not current_cache:
            cache_info = redis_store.get(cache_key)
            cache_plc_status = (
                json.loads(cache_info) if cache_info is not None else current_cache
            )
        else:
            cache_plc_status = current_cache

        current_cache = cache_plc_status

        if not current_cache:
            return None, current_cache

        attack_status = None

        # 风电模板
        if all(
                [
                    range_template_type == rangeModel.Range.RangeTemplate.wind_power.name,
                ]
        ):
            plc_name_map = {
                "c8cb35c8-c25d-43fd-8af3-cda146052246": "wincc1_PLC连接",
                "334c2314-393d-4df5-84b8-f1dc6e96a73f": "wincc2_PLC连接",
            }
            if vm.image in plc_name_map:
                plc_status_map = current_cache["data"]
                attack_status = (
                    2 if plc_status_map.get(plc_name_map[vm.image], None) else None
                )
        # 核电模板
        elif all(
                [
                    range_template_type
                    == rangeModel.Range.RangeTemplate.nuclear_power.name,
                ]
        ):
            # 核电目前仅一个plc，直接进行判定
            plc_status_map = current_cache["data"]
            if len(plc_status_map) == 1:
                attack_status = self.AttackStatus.breaking
            else:
                current_app.logger.warning(
                    f"靶场 {vm.range_uuid} "
                    f"模板为 '{rangeModel.Range.RangeTemplate.nuclear_power.value}' 存在多个PLC，不符合预设"
                )
        # 交通模板
        elif all(
                [range_template_type == rangeModel.Range.RangeTemplate.traffic_signals.name]
        ):
            # 风电目前仅一个plc，直接进行判定
            plc_status_map = current_cache["data"]
            if len(plc_status_map) == 1:
                attack_status = self.AttackStatus.breaking
            else:
                current_app.logger.warning(
                    f"靶场 {vm.range_uuid} "
                    f"模板为 '{rangeModel.Range.RangeTemplate.nuclear_power.value}' 存在多个PLC，不符合预设"
                )
        return attack_status, current_cache

    def get(self, ctf_id=None):
        ctf = self.check_ctf_red_blue_exists(ctf_id)

        if ctf.status != ctf.StatusChoices.in_progress.value:
            abort(400, message="比赛不是进行中状态")
        if not ctf.range_uuid:
            abort(400, message="该比赛未关联靶场")

        used_flag: List[CtfRedBlueFlag] = (
            CtfRedBlueFlag.query.filter(
                CtfRedBlueFlag.ctf_red_blue_id == ctf_id, CtfRedBlueFlag.is_used == 1
            )
            .order_by(CtfRedBlueFlag.update_time.desc())
            .all()
        )

        breaking_machine = {}
        range_link_info = rangeModel.RangeLinkModule.query.filter(
            ctf.range_uuid == rangeModel.RangeLinkModule.range_uuid
        ).first()

        if used_flag:
            # is_master的flag被发现，默认该虚拟机被攻停
            breaking_machine = {val.image_id for val in used_flag if val.is_master}

        # 3D 模块攻击展示信息
        attacked = {}

        current_cache = None
        # 获取所有flag被回答的vm镜像ID
        all_attacked_vm_image = {item.image_id for item in used_flag}
        # 循环判断靶场中虚拟机攻击状态
        for item in range_link_info.vmList:
            flag_mark = item.flag_3d_mark or item.vm_name

            # 攻停状态判断
            attack_status = None

            # 检查靶场中plc攻停状态
            if "plc" in item.type:
                # 目前只能按不同靶场写死
                attack_status, current_cache = self.template_shunt(
                    range_link_info.Range.range_template_type, current_cache, item
                )
            if attack_status is None:
                if item.image in breaking_machine:
                    attack_status = self.AttackStatus.breaking
                elif item.image in all_attacked_vm_image:
                    attack_status = self.AttackStatus.attacked
                else:
                    attack_status = self.AttackStatus.normal
            attacked[flag_mark] = attack_status

        # 获取具体flag提交信息
        attack_info = [
            {
                "user_id": item.link_score_info.relate_user.id,
                "user_name": item.link_score_info.relate_user.account_name,
                "msg": item.describe,
                "submit_time": item.update_time.timestamp(),
            }
            for item in used_flag
            if item.link_score_info
        ]

        return {"code": 200, "data": {"attacked": attacked, "attack_info": attack_info}}


class CTFRedBlueManage(MathManage):
    def get(self, ctf_red_blue_id=None):
        """红蓝比赛列表/比赛详情"""
        ctf: CtfRedBlue = self.check_ctf_red_blue_exists(ctf_red_blue_id)

        data = ctf.to_dict()

        for item in data.pop("groups"):
            data[f"{item.group_type}_group"] = [
                {
                    "id": val.id,
                    "username": val.username,
                    "account_name": val.account_name,
                    "email": val.email,
                }
                for val in item.relate_user
            ]
            data[f"{item.group_type}_group"].sort(key=lambda x: x["id"])

        rb_pilot = User.query.get(ctf.rb_pilot_id)
        data["rb_pilot"] = {
            "id": rb_pilot.id,
            "username": rb_pilot.username,
            "realname": rb_pilot.account_name,
            "email": rb_pilot.email,
        }
        rb_referee = User.query.get(ctf.rb_referee_id)
        data["rb_referee"] = {
            "id": rb_referee.id,
            "username": rb_referee.username,
            "realname": rb_referee.account_name,
            "email": rb_referee.email,
        }

        return {"code": 200, "data": data}

    def default_check(self, ctf_id=None):
        parser = reqparse.RequestParser()
        parser.add_argument("name", type=str, help="比赛名称不能为空", required=True)
        parser.add_argument("describe", type=str, help="比赛描述", required=True)
        parser.add_argument("rb_pilot_id", type=int, help="导调不能为空", required=True)
        parser.add_argument("rb_referee_id", type=int, help="裁判不能为空", required=True)
        parser.add_argument(
            "red_group", type=json.loads, help="红方用户列表应是字符串列表且不为空", required=True
        )
        parser.add_argument(
            "blue_group", type=json.loads, help="蓝方用户列表应是字符串列表且不为空", required=True
        )
        parser.add_argument("start_date", type=int, help="开始时间不能为空", required=True)
        parser.add_argument("end_date", type=int, help="结束时间不能为空", required=True)
        parser.add_argument(
            "red_initial_score", default=0, type=int, help="红队初始分不能为空", required=False
        )
        parser.add_argument(
            "blue_initial_score", default=0, type=int, help="蓝队初始分不能为空", required=False
        )
        args = parser.parse_args()

        # 比赛时长最长1周限制
        limiter = 86400 * 7
        if args["end_date"] - args["start_date"] > limiter:
            abort(400, message="请控制比赛时长在一周以内！")

        search = [CtfRedBlue.name == args["name"]]
        if ctf_id:
            search.append(CtfRedBlue.id != ctf_id)

        if CtfRedBlue.query.filter(*search).count():
            abort(400, message="比赛名称重复")

        return args

    def post(self):
        args = self.default_check()

        if set(args["red_group"]) & set(args["blue_group"]):
            abort(400, message="不允许用户同时参与红蓝方")

        try:
            red_group = CtfRedBlueGroup(group_type=GroupType.red.value)
            blue_group = CtfRedBlueGroup(group_type=GroupType.blue.value)

            red_group.relate_user = User.query.filter(
                User.id.in_(args["red_group"])
            ).all()
            blue_group.relate_user = User.query.filter(
                User.id.in_(args["blue_group"])
            ).all()

            db.session.add_all([red_group, blue_group])
            db.session.commit()

            ctf = CtfRedBlue(
                name=args["name"],
                describe=args["describe"],
                start_date=args["start_date"],
                end_date=args["end_date"],
                groups=[red_group, blue_group],
                rb_pilot_id=args["rb_pilot_id"],
                rb_referee_id=args["rb_referee_id"],
                create_user_id=self.user.id,
            )
            db.session.add(ctf)
            db.session.commit()
        except Exception as e:
            db.session.rollback()
            current_app.logger.exception(e)
            abort(500, message="保存失败")
        else:
            g.ctf_red_blue_id = ctf.id
            self.celery_task_red_blue(
                ctf.id,
                args,
                args["start_date"],
                args["end_date"],
            )
            return {
                "code": 200,
                "data": {"ctf_id": ctf.id, "msg": f"比赛{args['name']}创建成功"},
            }

    def put(self, ctf_red_blue_id=None):

        ctf = self.check_ctf_red_blue_exists(ctf_red_blue_id)
        if ctf.status != ctf.StatusChoices.wait_start.value:
            abort(400, message="当前状态不允许修改")
        args: dict = self.default_check(ctf_id=ctf_red_blue_id)
        if ctf.status == CtfRedBlue.StatusChoices.in_progress.value:
            return {"code": 400, "data": "不能修改已开启的比赛！"}

        try:
            ctf.name = args["name"]
            ctf.start_date = args["start_date"]
            ctf.end_date = args["end_date"]
            ctf.describe = args["describe"]
            ctf.red_initial_score = (
                args["red_initial_score"]
                if args["red_initial_score"]
                else ctf.red_initial_score
            )
            ctf.blue_initial_score = (
                args["blue_initial_score"]
                if args["blue_initial_score"]
                else ctf.blue_initial_score
            )
            ctf.rb_pilot_id = args["rb_pilot_id"]
            ctf.rb_referee_id = args["rb_referee_id"]

            groups = ctf.groups
            for item in groups:
                item.relate_user = [
                    User.query.get(val) for val in args[f"{item.group_type}_group"]
                ]
                db.session.add(item)
                db.session.commit()

            ctf.groups = groups
            db.session.add(ctf)
            db.session.commit()
        except Exception as e:
            db.session.rollback()
            current_app.logger.exception(e)
            abort(500, message="修改失败")
        else:
            self.celery_task_red_blue(
                ctf.id,
                args,
                args["start_date"],
                args["end_date"],
            )
            return {"code": 200, "data": f"比赛{args['name']}修改成功"}

    def delete(self, ctf_red_blue_id=None):
        ctf = self.check_ctf_red_blue_exists(ctf_red_blue_id)
        if ctf.status == CtfRedBlue.StatusChoices.in_progress.value:
            abort(400, message="不能删除已开启的比赛！")

        # 取消比赛启动停止任务
        revoke_task([f"CtfRedBlueStart__{ctf.id}", f"CtfRedBlueCLose__{ctf.id}"])

        with db.auto_commit():
            with db.session.begin(subtransactions=True):
                # 删除比赛数据
                link_score_info_query = LinkCtfRedBlueScoreInfo.query.filter_by(
                    ctf_id=ctf_red_blue_id
                )
                for item in link_score_info_query.all():
                    if item.manual_audit_info:
                        db.session.delete(item.manual_audit_info)
                    if item.score_info:
                        db.session.delete(item.score_info)
                    if item.flag_info:
                        db.session.delete(item.flag_info)
                    db.session.delete(item)
                CtfRedBlueGroup.query.filter_by(
                    ctf_red_blue_id=ctf_red_blue_id
                ).delete()
                CtfRedBlueFlag.query.filter_by(ctf_red_blue_id=ctf_red_blue_id).delete()
                CtfRedBlue.query.filter_by(id=ctf_red_blue_id).delete()
                # 删除比赛每小时比分对比数据
                ctfModel.CtfScore.query.filter_by(ctf_red_blue_id=ctf_red_blue_id).delete()

        current_app.logger.info(
            f"{self.user.account_name}删除红蓝比赛,ctf-red-blue-name: {ctf.name}  ctf-red-blue-id: {ctf_red_blue_id}"
            # noqa
        )

        return {"code": 200, "data": "删除成功！"}


class CTFRedBlueClose(MathManage):
    """ctf红蓝比赛手动关闭"""

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("ctf_id", type=int, help="缺少红蓝对抗比赛ID", required=True)
        param = parser.parse_args()

        ctf_id = param["ctf_id"]

        self.check_ctf_red_blue_exists(ctf_id)
        current_app.logger.info(
            f"{self.user.account_name}关闭ctf红蓝比赛, ctf-red-blue-id: {ctf_id}"
        )

        revoke_task([f"CtfRedBlueCLose__{ctf_id}"])
        close_ctf_red_blue(ctf_id, msg="导调关闭比赛")

        return {"code": 200, "data": "操作成功"}


# ctf红蓝公告相关接口
class CTFRedBlueNotice(MathManage):

    def get(self):
        """
        查看公告
        """
        params = request.args
        notice_id = params.get("id", type=int)
        page = params.get("page", type=int, default=1)
        page_size = params.get("pageSize", type=int, default=10)
        current_role = request.cookies.get("role", None)
        # 查看单个公告详情
        if current_role != PresetRoleChoices.teacher:
            return {"code": 200, "message": "没有查看权限！"}
        if notice_id:
            row = db.session.query(CtfNotice).filter_by(id=notice_id).first()
            notice_status_info_dict = json.loads(row.notice_status_info)
            num = sum(
                1
                for i in list(notice_status_info_dict.keys())
                if 'id_' in i and notice_status_info_dict[i] == 1
            )
            result_data = {
                "id": row.id,
                "title": row.title,
                "content": row.content,
                "level": row.level,
                "visual_range": CtfRedBlue.query.filter_by(
                    id=row.visual_range).first().name if row.visual_range != 0 else "全部学生",
                "receive_num": len(notice_status_info_dict.keys()) // 2,
                "look_num": num,
                "create_time": str(row.create_time)
            }
            return {"code": 200, "data": result_data}

        # 如果角色是教师，查看全部公告信息
        if current_role == PresetRoleChoices.teacher:
            # 查询所有正在进行的比赛，并找出所有比赛关联的学生
            filters = []
            return self._extracted_from_get_(filters, page_size, page)
        else:
            return {"code": 200, "message": "没有查看权限！"}

    # TODO Rename this here and in `get`
    def _extracted_from_get_(self, filters, page_size, page):
        filters.extend(
            (CtfRedBlue.status == 1, CtfRedBlue.create_user_id == self.user.id)
        )
        ctf_objs = CtfRedBlue.query.filter(*filters).all()
        data = [item.to_dict() for item in ctf_objs]
        for item in data:
            for val in item.pop("groups"):
                item[f"{val.group_type}_group"] = [
                    {
                        "id": v.id
                    }
                    for v in val.relate_user
                ]
        notice_rows = db.session.query(CtfNotice).order_by(CtfNotice.create_time.desc()) \
                .limit(page_size).offset(page_size * (page - 1)).all()
        total = db.session.query(CtfNotice).count()
        num_list = [len(d["blue_group"]) + len(d["red_group"]) for d in data]
        result_data_list = []
        for row in notice_rows:
                # 可见范围是全部学生
            if row.visual_range == 0:
                notice_status_info_dict = json.loads(row.notice_status_info)
                num = sum(
                    1
                    for i in list(notice_status_info_dict.keys())
                    if 'id_' in i and notice_status_info_dict[i] == 1
                )
                result_data = {
                    "id": row.id,
                    "title": row.title,
                    "content": row.content,
                    "level": row.level,
                    "visual_range": CtfRedBlue.query.filter_by(
                        id=row.visual_range).first().name if row.visual_range != 0 else "全部学生",
                    "receive_num": sum(num_list),
                    "look_num": num,
                    "create_time": str(row.create_time)
                }
                result_data_list.append(result_data)
            else:
                ctf_row = CtfRedBlue.query.filter_by(id=row.visual_range).first()
                if ctf_row:
                    item_dict = {
                        f"{val.group_type}_group": [
                            {"id": v.id} for v in val.relate_user
                        ]
                        for val in ctf_row.to_dict().pop("groups")
                    }
                    notice_status_info_dict = json.loads(row.notice_status_info)
                    num = sum(
                        1
                        for i in list(notice_status_info_dict.keys())
                        if 'id_' in i and notice_status_info_dict[i] == 1
                    )
                    result_data = {
                        "id": row.id,
                        "title": row.title,
                        "content": row.content,
                        "level": row.level,
                        "visual_range": CtfRedBlue.query.filter_by(
                            id=row.visual_range).first().name if row.visual_range != 0 else "全部学生",
                        "receive_num": (len(item_dict['blue_group']) + len(item_dict['red_group'])),
                        "look_num": num,
                        "create_time": str(row.create_time)
                    }
                    result_data_list.append(result_data)
        return {"code": 200, "data": result_data_list, "total": total}

    def post(self):
        """
        添加公告
        """
        parser = reqparse.RequestParser()
        parser.add_argument("title", type=str, help="公告标题", required=True)
        parser.add_argument("content", type=str, help="公告内容", required=True)
        parser.add_argument("level", type=int, help="公告等级", required=True)
        parser.add_argument("visual_range", type=int, help="可见范围", required=True)
        args = parser.parse_args()
        try:
            notice_status_info_ = {}
            if args["visual_range"] == 0:
                ctf_objs = CtfRedBlue.query.filter_by(status=1).all()
                data = [item.to_dict() for item in ctf_objs]
                for item in data:
                    for val in item.pop("groups"):
                        item[f"{val.group_type}_group"] = [
                            {
                                v.id: "id"
                            }
                            for v in val.relate_user
                        ]
                for d in data:
                    for i in (d["blue_group"] + d["red_group"]):
                        notice_status_info_.update(i)
                        notice_status_info_.update({'id_{}'.format(list(i.keys())[0]): 0})
            else:
                ctf_row = CtfRedBlue.query.filter_by(id=args['visual_range']).first()
                item_dict = {}
                for val in ctf_row.to_dict().pop("groups"):
                    item_dict[f"{val.group_type}_group"] = [
                        {
                            v.id: "id"
                        }
                        for v in val.relate_user
                    ]
                for i in (item_dict['blue_group'] + item_dict['red_group']):
                    notice_status_info_.update(i)
                    notice_status_info_.update({'id_{}'.format(list(i.keys())[0]): 0})
            ctf_notice = CtfNotice(
                title=args["title"],
                content=args["content"],
                level=args["level"],
                visual_range=args["visual_range"],
                notice_status_info=json.dumps(notice_status_info_)
            )
            db.session.add(ctf_notice)
            db.session.commit()
            return {"code": 200, "message": "添加公告完成"}
        except Exception as e:
            db.session.rollback()
            current_app.logger.exception(e)
            abort(500, message="添加公告失败！")

    def delete(self):
        """
        删除公告
        """
        parser = reqparse.RequestParser()
        parser.add_argument("id", type=int, help="公告id", required=True)
        args = parser.parse_args()
        try:
            notice_row = db.session.query(CtfNotice).filter_by(id=args["id"]).first()
            if notice_row:
                db.session.query(CtfNotice).filter_by(id=args["id"]).delete()
                db.session.commit()
            return {"code": 200, "message": "删除公告完成"}
        except Exception as e:
            db.session.rollback()
            current_app.logger.exception(e)
            abort(500, message="删除公告失败！")


class CTFRedBlueManageList(MathManage):
    def get(self):
        """红蓝比赛列表"""

        params = request.args
        page = params.get("page", type=int, default=1)
        page_size = params.get("pageSize", type=int, default=10)
        # 多选
        status = params.get("status", type=str)
        keyword = params.get("keyword", type=str)

        filters = []
        if keyword:
            filters.append(CtfRedBlue.name.contains(keyword))
        if status:
            filters.append(CtfRedBlue.status == status)
        # 观众只能查看进行中比赛
        current_role = request.cookies.get("role", None)
        if current_role is None:
            abort(400, message="cookie获取失败")
        if current_role == PresetRoleChoices.audience:
            filters.append(
                CtfRedBlue.status == CtfRedBlue.StatusChoices.in_progress.value
            )
        elif current_role == PresetRoleChoices.rb_pilot:
            filters.append(CtfRedBlue.rb_pilot_id == self.user.id)
        elif current_role == PresetRoleChoices.teacher:
            filters.append(CtfRedBlue.create_user_id == self.user.id)

        ctf_objs = (
            CtfRedBlue.query.filter(*filters)
            .order_by(CtfRedBlue.id.desc())
            .paginate(page, page_size, error_out=False)
        )

        data = {
            "current_page": page,
            "data": [item.to_dict() for item in ctf_objs.items],
        }
        for item in data["data"]:
            for val in item.pop("groups"):
                item[f"{val.group_type}_group"] = [
                    {
                        "id": v.id,
                        "username": v.username,
                        "account_name": v.account_name,
                        "email": v.email,
                    }
                    for v in val.relate_user
                ]
                item[f"{val.group_type}_group"].sort(key=lambda x: x["id"])

            # 比赛列表返回关联靶场的模板类型，便于前端3D模型渲染
            if item["range_uuid"]:
                _ranges = (
                    rangeModel.Range.query.join(
                        rangeModel.RangeLinkModule,
                        rangeModel.RangeLinkModule.range_id == rangeModel.Range.id,
                    )
                    .join(
                        CtfRedBlue,
                        and_(
                            CtfRedBlue.range_uuid
                            == rangeModel.RangeLinkModule.range_uuid,
                            CtfRedBlue.id == item["id"],
                        ),
                    )
                    .filter(rangeModel.Range.is_system == 0)
                )
                if _ranges.count() != 1:
                    abort(500, message=f"ctf红蓝比赛{item['id']}关联靶场数据查询异常")
                item["range_template_type"] = _ranges.first().range_template_type
            else:
                item["range_template_type"] = ""

        # 增加分页信息
        base_detail(data, ctf_objs)

        return {"code": 200, "data": data}


class CTFRedBlueScoreInfo(MathManage):
    def get_countdown(self, end_date):
        """
        比赛倒计时
        """
        ctime = int(time.time())
        return max(end_date - ctime, 0)

    def get(self, ctf_red_blue_id):
        ctf = self.check_ctf_red_blue_exists(ctf_red_blue_id)
        if not ctf:
            abort(400, message="比赛不存在")
        # 获取比赛倒计时
        countdown = self.get_countdown(ctf.end_date)
        result_data = {"countdown": countdown, "type": "红蓝对抗"}
        # 赛题数量
        questions_total = ctfModel.CtfRedBlueFlag.query.filter_by(ctf_red_blue_id=ctf_red_blue_id).count()
        result_data["questions_total"] = questions_total
        questions_rows = ctfModel.CtfRedBlueFlag.query.filter_by(ctf_red_blue_id=ctf_red_blue_id).all()
        score_total = sum(row.score for row in questions_rows)
        result_data["score_total"] = score_total
        result_data["ctf_red_blue_id"] = ctf_red_blue_id
        return {"code": 200, "data": result_data}


class CTFRedBlueScore(MathManage):
    """ctf红蓝攻防成绩"""

    @staticmethod
    def group_score(group: CtfRedBlueGroup, initial_score=0):
        """计算团队总成绩"""
        score_info: List[LinkCtfRedBlueScoreInfo] = group.link_score
        score_record = [item.score_info for item in score_info]

        score_result = defaultdict(list)
        for item in score_record:
            if item:
                score_result[item.change_type].append(item.score)

        plus_score = sum(score_result[CtfRedBlueScore.ChangeTypeEnum.plus.name] or [0])
        less_score = sum(score_result[CtfRedBlueScore.ChangeTypeEnum.less.name] or [0])

        return initial_score + plus_score - less_score

    def get_one_ctf_score(self, **kwargs):
        """获取某个比赛各个团队成绩"""
        ctf = self.check_ctf_red_blue_exists(kwargs["ctf_red_blue_id"])
        if not ctf:
            abort(400, message="比赛不存在")

        groups: List[CtfRedBlueGroup] = ctf.groups

        res = {}
        red_initial_score = ctf.red_initial_score
        blue_initial_score = ctf.blue_initial_score
        for item in groups:
            initial_score = (
                red_initial_score
                if item.group_type == GroupType.red.value
                else blue_initial_score
            )
            sorce = self.group_score(item, initial_score=initial_score)
            res[f"{item.group_type}"] = {"id": item.id, "sorce": sorce}

        ctf_score = OrderedDict(
            sorted(res.items(), key=lambda x: x[1]["sorce"], reverse=True)
        )
        for item in ctf_score:
            ctf_score[item]["user_score"] = self.get_one_group_score(
                group_id=ctf_score[item]["id"], ctf_id=kwargs["ctf_red_blue_id"]
            )

        return ctf_score

    def get_one_group_score(self, **kwargs):
        """获取某个团队各个成员成绩
        最低为0"""
        group_id = kwargs["group_id"]

        res = CtfRedBlueGroup.query.filter(CtfRedBlueGroup.id == group_id)
        if res.count() != 1:
            abort(400, message="查询到0条或多条团队数据，请检查!")

        group = res.first()
        user_info = [
            {
                "id": val.id,
                "username": val.username,
                "account_name": val.account_name,
                "email": val.email,
                "questions": ctfModel.LinkCtfRedBlueScoreInfo.query.filter_by(user_id=val.id,
                                                                              ctf_id=kwargs["ctf_id"]).count(),
                "class_name": classesModel.Classes.query.filter_by(id=val.class_id).first().name
                if classesModel.Classes.query.filter_by(id=val.class_id).first() else ""
            }
            for val in group.relate_user
        ]

        score_info: List[LinkCtfRedBlueScoreInfo] = group.link_score

        user_score = defaultdict(list)
        _conver = (
            lambda x, y: x if y == CtfRedBlueScore.ChangeTypeEnum.plus.name else -x
        )

        for item in score_info:
            if item.score_info:
                user_score[item.user_id].append(
                    _conver(item.score_info.score, item.score_info.change_type)
                )

        for item in user_info:
            score_list = user_score.get(item["id"]) or [0]
            item["score"] = sum(score_list)

        user_info.sort(key=lambda x: x["score"], reverse=True)

        return user_info

    def get_all_run_ctf_score(self, **kwargs):
        """获取所有进行中红蓝ctf比赛各个团队成绩
        按比赛id正序，每个比赛内团队成绩倒序
        """
        ctf_list = CtfRedBlue.query.filter(
            CtfRedBlue.status == CtfRedBlue.StatusChoices.in_progress.value
        )
        if not ctf_list.count():
            return []

        return {
            f"{ctf.id}": self.get_one_ctf_score(ctf_red_blue_id=ctf.id)
            for ctf in ctf_list
        }

    def get_all_commit_question_type_total(self, ctf_red_blue_id):
        misc = 0
        crypto = 0
        pwn = 0
        reverse = 0
        industrial_control = 0
        blue_misc = 0
        blue_crypto = 0
        blue_pwn = 0
        blue_reverse = 0
        blue_industrial_control = 0
        flag_rows = ctfModel.CtfRedBlueFlag.query.filter(CtfRedBlueFlag.ctf_red_blue_id == ctf_red_blue_id,
                                                         CtfRedBlueFlag.is_used == True, CtfRedBlueFlag.type == "red"
                                                         ).all()
        if flag_rows:
            for flag in flag_rows:
                type = flag.ctf_question_type
                if type == 1:
                    misc += 1
                elif type == 2:
                    crypto += 1
                elif type == 3:
                    pwn += 1
                elif type == 4:
                    reverse += 1
                elif type == 5:
                    industrial_control += 1
        red_question_type = {"misc_count": misc, "crypto_count": crypto, "pwn_count": pwn, "reverse_count": reverse,
                             "industrial_control_count": industrial_control}

        flag_rows_ = ctfModel.CtfRedBlueFlag.query.filter(CtfRedBlueFlag.ctf_red_blue_id == ctf_red_blue_id,
                                                          CtfRedBlueFlag.is_used == True, CtfRedBlueFlag.type == "blue"
                                                          ).all()
        if flag_rows_:
            for flag in flag_rows_:
                type = flag.ctf_question_type
                if type == 1:
                    blue_misc += 1
                elif type == 2:
                    blue_crypto += 1
                elif type == 3:
                    blue_pwn += 1
                elif type == 4:
                    blue_reverse += 1
                elif type == 5:
                    blue_industrial_control += 1
        blue_question_type = {"misc_count": blue_misc, "crypto_count": blue_crypto, "pwn_count": blue_pwn,
                              "reverse_count": blue_reverse, "industrial_control_count": blue_industrial_control}
        return {"red_question_type": red_question_type, "blue_question_type": blue_question_type}

    def get(self, ctf_red_blue_id=None, ctf_red_blue_group_id=None):
        """获取比赛各个团队、各个成员成绩"""

        func_map = {
            (False, False): self.get_all_run_ctf_score,
            (True, False): self.get_one_ctf_score,
            (True, True): self.get_one_group_score,
        }

        data = func_map[(bool(ctf_red_blue_id), bool(ctf_red_blue_group_id))](
            ctf_red_blue_id=ctf_red_blue_id, group_id=ctf_red_blue_group_id
        )
        data['ctf_red_blue_id'] = ctf_red_blue_id
        print('8988888:', self.get_all_commit_question_type_total(ctf_red_blue_id))
        data['red_question_type'] = self.get_all_commit_question_type_total(ctf_red_blue_id)['red_question_type']
        data['blue_question_type'] = self.get_all_commit_question_type_total(ctf_red_blue_id)['blue_question_type']
        return {"code": 200, "data": data}


class CTFManualJudgment(MathManage):
    """人工裁决接口"""

    @staticmethod
    def get_audit_url(ctf_info: CtfRedBlue):
        wazuh_address = suricata_address = ""
        range_link_module_info = rangeModel.RangeLinkModule.query.filter(
            rangeModel.RangeLinkModule.range_uuid == ctf_info.range_uuid
        ).first()
        if not range_link_module_info:
            return wazuh_address, suricata_address

        wazuh_vm = rangeModel.RangeVm.query.filter(
            rangeModel.RangeVm.type == "HIDS",
            rangeModel.RangeVm.range_uuid == range_link_module_info.range_uuid,
        ).first()
        suricata_vm = rangeModel.RangeVm.query.filter(
            rangeModel.RangeVm.type == "NIDS",
            rangeModel.RangeVm.range_uuid == range_link_module_info.range_uuid,
        ).first()

        def get_msg(obj, vm_type):
            if not obj:
                return 0, f"{vm_type}不存在"
            elif not obj.op_vm_uuid:
                return 0, f"{obj.vm_name}未开机"
            else:
                return 1, ""

        status, msg = get_msg(wazuh_vm, "HIDS")
        wazuh_address_info = {
            "range_uuid": range_link_module_info.range_uuid,
            "vm_uuid": wazuh_vm.vm_uuid if wazuh_vm else "",
            "msg": msg,
            "status": status,
        }
        status, msg = get_msg(suricata_vm, "NIDS")
        suricata_address_info = {
            "range_uuid": range_link_module_info.range_uuid,
            "vm_uuid": suricata_vm.vm_uuid if suricata_vm else "",
            "msg": msg,
            "status": status,
        }

        return wazuh_address_info, suricata_address_info

    def get(self, ctf_red_blue_id=None):
        """获取需要裁决的信息列表(全部 or 详细)"""

        params = request.args
        audit_id = params.get("audit_id", default=None, type=int)
        is_processed = params.get("is_processed", default=0, type=int)
        keyword = params.get("keyword", default=None, type=str)
        # 列表
        if audit_id is None:
            page = params.get("page", 1, type=int)
            page_size = params.get("pageSize", 10, type=int)
            _order_by = (
                CtfRedBlueManualAudit.create_time.desc()
                if is_processed
                else CtfRedBlueManualAudit.create_time.asc()
            )

            manual_audit = (
                CtfRedBlueManualAudit.query.join(
                    LinkCtfRedBlueScoreInfo,
                    LinkCtfRedBlueScoreInfo.id == CtfRedBlueManualAudit.link_info_id,
                )
                .join(
                    CtfRedBlue,
                    and_(
                        CtfRedBlue.id == LinkCtfRedBlueScoreInfo.ctf_id,
                        CtfRedBlue.rb_referee_id == self.user.id,
                    ),
                )
                .filter(CtfRedBlueManualAudit.is_processed == is_processed)
            )

            if keyword:
                manual_audit = manual_audit.join(
                    CtfRedBlue, LinkCtfRedBlueScoreInfo.ctf_id == CtfRedBlue.id
                ).filter(CtfRedBlue.name.contains(keyword))

            manual_audit = manual_audit.order_by(_order_by).paginate(
                page, page_size, error_out=False
            )

            data = {}
            data["current_page"] = page
            data["data"] = []
            if len(manual_audit.items) > 0:
                for item in manual_audit.items:
                    ctf_info: CtfRedBlue = item.link_score_info.ctf_red_blue
                    info = dict(  # noqa
                        id=item.id,
                        title=item.title,
                        apply_describe=item.apply_describe,
                        create_time=datetime.timestamp(item.create_time),
                        update_time=datetime.timestamp(item.update_time),
                        user_name=item.link_score_info.relate_user.account_name,
                        is_processed=is_processed,
                        ctf_red_blue_id=ctf_info.id,
                        ctf_red_blue_name=ctf_info.name,
                        describe=ctf_info.describe,
                    )
                    (
                        info["wazuh_address_info"],
                        info["suricata_address_info"],
                    ) = self.get_audit_url(ctf_info)
                    data["data"].append(info)
            data = base_detail(data, manual_audit)
            return {"code": 200, "data": data}
        # 详情
        else:
            manual_audit = (
                CtfRedBlueManualAudit.query.join(
                    LinkCtfRedBlueScoreInfo,
                    LinkCtfRedBlueScoreInfo.id == CtfRedBlueManualAudit.link_info_id,
                )
                .join(
                    CtfRedBlue,
                    and_(
                        CtfRedBlue.id == LinkCtfRedBlueScoreInfo.ctf_id,
                        CtfRedBlue.rb_referee_id == self.user.id,
                    ),
                )
                .filter(CtfRedBlueManualAudit.id == audit_id)
            )

            if manual_audit.count() != 1:
                abort(400, message="查询到0条或多条重复数据，请检查!")

            data = manual_audit.first()
            ctf_info: CtfRedBlue = data.link_score_info.ctf_red_blue

            return {
                "code": 200,
                "data": {
                    "id": data.id,
                    "title": data.title,
                    "apply_describe": data.apply_describe,
                    "audit_describe": data.audit_describe,
                    "is_success": data.is_success,
                    "create_time": datetime.timestamp(data.create_time),
                    "ctf_red_blue_id": ctf_info.id,
                    "ctf_red_blue_name": ctf_info.name,
                    "current_group": data.link_score_info.ctf_red_blue_group.group_type,
                    "account_name": data.link_score_info.relate_user.account_name,
                },
            }

    @staticmethod
    def close_other_manual_judgment(ctf_id, msg, user_id=None):
        other_audit = (
            CtfRedBlueManualAudit.query.join(
                LinkCtfRedBlueScoreInfo,
                LinkCtfRedBlueScoreInfo.id == CtfRedBlueManualAudit.link_info_id,
            )
            .filter(
                CtfRedBlueManualAudit.is_processed == 0,
                LinkCtfRedBlueScoreInfo.ctf_id == ctf_id,
            )
            .all()
        )
        for item in other_audit:
            item.is_processed = 1
            item.audit_describe = msg
            if user_id:
                item.audit_user_id = user_id
        db.session.bulk_save_objects(other_audit)

    def post(self):
        """裁决判定"""
        parser = reqparse.RequestParser()
        parser.add_argument(
            "ctf_red_blue_id", type=int, help="缺少红蓝对抗比赛ID", required=True
        )
        parser.add_argument("audit_id", type=str, help="缺少裁决条目", required=True)
        parser.add_argument("audit_describe", type=str, help="必须描述判定理由", required=True)
        parser.add_argument("score", default=10, type=int, help="分数", required=False)
        parser.add_argument("is_success", type=int, help="必须提供裁决结果", required=True)
        parser.add_argument("is_over", type=int, help="比赛是否结束", required=False)
        param = parser.parse_args()

        ctf = self.check_ctf_red_blue_exists(param["ctf_red_blue_id"])
        if ctf.status != ctf.StatusChoices.in_progress.value:
            abort(400, message="比赛非进行中")

        manual_audit = CtfRedBlueManualAudit.query.join(
            LinkCtfRedBlueScoreInfo,
            LinkCtfRedBlueScoreInfo.id == CtfRedBlueManualAudit.link_info_id,
        ).filter(
            CtfRedBlueManualAudit.id == param["audit_id"],
            LinkCtfRedBlueScoreInfo.ctf_id == param["ctf_red_blue_id"],
        )

        if manual_audit.count() != 1:
            abort(400, message="查询到0条或多条重复数据，请检查!")

        audit: CtfRedBlueManualAudit = manual_audit.first()
        if audit.score:
            abort(400, message="无法重复裁决")

        with db.auto_commit():
            with db.session.begin(subtransactions=True):
                response = self.update_audit_info(audit, ctf, param)

        return response

    def over_ctf(self, audit: CtfRedBlueManualAudit, ctf: CtfRedBlue):
        """结束CTF"""
        score_record = CtfRedBlueScore(
            score=99999,
            change_type=CtfRedBlueScore.ChangeTypeEnum.plus.name,
            record_type=CtfRedBlueScore.RecordTypeEnum.manual.name,
        )
        db.session.add(score_record)
        # 关联更新
        link_info = LinkCtfRedBlueScoreInfo.query.get(audit.link_info_id)
        link_info.score_info = score_record
        # 更新比赛胜利方
        group_type = link_info.ctf_red_blue_group.group_type
        ctf.king = group_type
        ctf.status = ctf.StatusChoices.closed.value

        # 将该比赛其他申请置为已处理
        self.close_other_manual_judgment(
            ctf.id, f"{group_type} 方申请裁决编号{audit.id} 判定比赛结束", user_id=self.user.id
        )

        return {"code": 200, "data": f"裁决成功, 比赛已结束，{group_type} 方胜利"}

    def update_audit_info(
            self, audit: CtfRedBlueManualAudit, ctf: CtfRedBlue, param: dict
    ):
        audit.referee_id = self.user.id
        audit.audit_describe = param["audit_describe"]

        if not param["is_success"]:
            audit.is_success = 0
            audit.is_processed = 1
            return {"code": 200, "data": "裁决成功，该申请已拒绝"}

        # 更新申请信息
        audit.is_success = 1
        audit.is_processed = 1
        audit.score = param["score"]

        # 判定胜利
        if param["is_over"]:
            return self.over_ctf(audit, ctf)

        # 创建红队得分记录
        score_record = CtfRedBlueScore(
            score=param["score"],
            change_type=CtfRedBlueScore.ChangeTypeEnum.plus.name,
            record_type=CtfRedBlueScore.RecordTypeEnum.manual.name,
        )
        db.session.add(score_record)

        # 更新红队关联信息
        link_info = LinkCtfRedBlueScoreInfo.query.get(audit.link_info_id)
        link_info.score_info = score_record

        # 如果是红队提交的人工裁决，创建蓝队扣分记录
        if link_info.ctf_red_blue_group.group_type == GroupType.red.name:
            blue_group = CtfRedBlueGroup.query.filter(
                CtfRedBlueGroup.ctf_red_blue_id == param["ctf_red_blue_id"],
                CtfRedBlueGroup.group_type == GroupType.blue.name,
            ).first()
            blue_record = CtfRedBlueScore(
                score=param["score"],
                change_type=CtfRedBlueScore.ChangeTypeEnum.less.name,
                record_type=CtfRedBlueScore.RecordTypeEnum.link.name,
            )
            db.session.add(blue_record)

            link_info = LinkCtfRedBlueScoreInfo(
                ctf_id=param["ctf_red_blue_id"],
                group_id=blue_group.id,
                score_info=blue_record,
            )
            db.session.add(link_info)

        return {"code": 200, "data": "裁决成功"}
